#pragma once
#include <gl/glew.h>
#include "technique.h"
#include "ogldev_util.h"
#include "ogldev_math_3d.h"
#include "ogldev_callbacks.h"
#include "ogldev_backend.h"
#include "ogldev_basic_mesh.h"

const int WIN_WIDTH = 800;
const int WIN_HEIGHT = 800;

class ShadowmapFBO
{
public:
	bool Init(){
		glGenFramebuffers(1, &m_fbo);

		glGenTextures(1, &m_shadowMap);
		glBindTexture(GL_TEXTURE_2D, m_shadowMap);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WIN_WIDTH, WIN_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);

		GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		if (status != GL_FRAMEBUFFER_COMPLETE){
			printf("FB error, status: 0x%x\n", status);
			return false;
		}

		return true;
	}

	void BindForWriting()
	{
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
	}


	void BindForReading(GLenum TextureUnit)
	{
		glActiveTexture(TextureUnit);
		glBindTexture(GL_TEXTURE_2D, m_shadowMap);
	}
private:
	GLuint m_fbo;
	GLuint m_shadowMap;
};

class SimpleColorTechnique : public Technique
{
public:
	bool Init(){
		if (!Technique::Init()) return false;

		if (AddShader(GL_VERTEX_SHADER, "../../resource/simple_color.vert")
			&& AddShader(GL_FRAGMENT_SHADER, "../../resource/simple_color.frag")
			&& Finalize())
		{
			gWVP = GetUniformLocation("gWVP");
			gColor = GetUniformLocation("gColor");
			return gWVP != INVALID_UNIFORM_LOCATION
				&& gColor != INVALID_UNIFORM_LOCATION;
		}
		return false;
	}
	void SetWVP(const Matrix4f& WVP){
		glUniformMatrix4fv(gWVP, 1, GL_TRUE, (const GLfloat*)&WVP);
	}
	void SetColor(const Vector3f& Color){
		glUniform3fv(gColor, 1, (const GLfloat*)&Color);
	}
private:
	GLuint gWVP;
	GLuint gColor;
};

class ShadowmapWithDirectionalLightTechnique : public Technique
{
public:
	bool Init(){
		if (!Technique::Init()) return false;

		if (AddShader(GL_VERTEX_SHADER, "../../resource/shadowmap_with_directional_light.vert")
			&& AddShader(GL_FRAGMENT_SHADER, "../../resource/shadowmap_with_directional_light.frag")
			&& Finalize())
		{
			gWVP = GetUniformLocation("gWVP");
			gWorld = GetUniformLocation("gWorld");
			gLightDir = GetUniformLocation("gLightDir");
			return gWVP != INVALID_UNIFORM_LOCATION
				&& gWorld != INVALID_UNIFORM_LOCATION
				&& gLightDir != INVALID_UNIFORM_LOCATION;
		}
		return false;
	}
	void SetWVP(const Matrix4f& WVP){
		glUniformMatrix4fv(gWVP, 1, GL_TRUE, (const GLfloat*)&WVP);
	}
	void SetWorld(const Matrix4f& World){
		glUniformMatrix4fv(gWorld, 1, GL_TRUE, (const GLfloat*)&World);
	}
	void SetLightDir(const Vector3f& LightDir){
		glUniform3fv(gLightDir, 1, (const GLfloat*)&LightDir);
	}
private:
	GLuint gWVP;
	GLuint gWorld;
	GLuint gLightDir;
};